class Rand {
    constructor(prng = Math.random) {
        this.prng = prng
    }

    position(dim) {
        const x = Math.floor(this.prng() * dim.width)
        const y = Math.floor(this.prng() * dim.height)
        return {x, y} // NOTE: object property shorthand, same as writing { x: x, y: y}
    }

    choice(arr) {
        return arr[Math.floor(this.prng() * arr.length)]
    }
}

class RandStyle extends Rand {
    constructor(playerPos, gemPos, prng = Math.random) {
        super(prng);
        this.playerPos = playerPos
        this.gemPos = gemPos
        this.cellStyle = ['cell']
        this.terrains = ['desert', 'forest', 'mountain', 'swamp']
    }

    #call(newPos) {
        if (this.playerPos.x === newPos.x && this.playerPos.y === newPos.y)
            return 'player'

        if (this.gemPos.x === newPos.x && this.gemPos.y === newPos.y)
            return 'diamond'

        // if (!tile.isVisited)
        //     return [tile.terrain, 'cloaked']
        return this.choice(this.terrains)
    }

    call(newPos) {
        return this.cellStyle.concat([this.#call(newPos)])
    }
}

function peekPositions(pos, width, height) {
    const row = pos.x
    const col = pos.y
    let offsets = [-1, 0, 1] // try changing this to [-2, -1, 0, 1, 2] and see what happens! Why?
    return offsets.map(i => offsets.map(j => [row + i, col + j]))
        .flat()
        .filter(([newRow, newCol]) => {
            return !(newRow === row && newCol === col)
                && newRow >= 0 && newRow < height
                && newCol >= 0 && newCol < width
        })
        .map(([x, y]) => {
            return {x, y}
        })
}

function nonOverlappingPositions(list1, list2) {
    // Use filter and every to find the positions that are not in the second list
    return list1.filter(position1 => list2.every(position2 => position1.x !== position2.x || position1.y !== position2.y));
}

class Tile {
    constructor(aTerrain, isVisited = false, npc= 0) {
        this.terrain = aTerrain
        this.isVisited = isVisited
        this.items = []
        this.npc = npc
    }
}

class GameMap {
    constructor(container, dims) {
        this.mapSize = dims
        this.mapWidth = dims.width
        this.mapHeight = dims.height

        this.container = container

        this.tiles = new Array(this.mapHeight)

        this.playerPos = {
            x: Math.floor(this.mapWidth / 2),
            y: Math.floor(this.mapHeight / 2)
        }
        this.changeList = []
        this.gemPos = new Rand(Math.random).position(this.mapSize)

        this.randStyle = new RandStyle(this.playerPos, this.gemPos)
    }

    newCell(x, y) {
        let cell = document.createElement('div')
        const styleClasses = this.randStyle.call({x: x, y: y})
        styleClasses.forEach((styleClass) => cell.classList.add(styleClass))
        return cell
    }

    build() {
        this.container.replaceChildren()

        for (let x = 0; x < this.mapHeight; x++){
            this.tiles[x] = new Array(this.mapWidth)
            for (let y = 0; y < this.mapWidth; y++){
                let cell = this.newCell(x, y)
                this.tiles[x][y] = new Tile(cell.classList[1])
                this.container.appendChild(cell)
            }
        }
    }

    nextCoordinates(direction) {
        let from = this.playerPos
        switch (direction) {
            case 'ArrowUp':
                return {x: from.x, y: from.y - 1}
            case 'ArrowDown':
                return {x: from.x, y: from.y + 1}
            case 'ArrowLeft':
                return {x: from.x - 1, y: from.y}
            case 'ArrowRight':
                return {x: from.x + 1, y: from.y}
            default:
                throw new Error('Unknown direction')
        }
    }

    setEventListener() {
        // this.handleEvent = this.state.handleEvent.bind(this.state)
        window.addEventListener('keydown', this.handleEvent)
    }

    addChangeListEntry(point, peek = false) {
        this.changeList.push({
            x: point.x,
            y: point.y,
            peek: peek
        });
    }

    moveFromTo(from, to) {
        const peekFromPos = peekPositions(from, this.mapWidth, this.mapHeight)
        const peekToPos = peekPositions(to, this.mapWidth, this.mapHeight)

        const leavingPeekPos = nonOverlappingPositions(peekFromPos, peekToPos)
        const enteringPeekPos = nonOverlappingPositions(peekToPos, peekFromPos)

        leavingPeekPos.forEach((pos) => {
            this.addChangeListEntry(pos)
        })
        enteringPeekPos.forEach((pos) => {
            this.addChangeListEntry(pos, true)
        })
        this.addChangeListEntry(from)
        this.addChangeListEntry(to)
        this.tileAt(to).isVisited = true
    }

    handleEvent(event) {
        // update_based_on_event: {
        switch (event.key) {
            case 'ArrowRight':
            case 'ArrowLeft':
            case 'ArrowDown':
            case 'ArrowUp':
                event.preventDefault()
                event.stopPropagation()
                // if (!this.gemFound() && this.isValidPlayerMove(this.game.gameMap.playerPos, event.key)) {
                const nextPosition = this.nextCoordinates(event.key)
                this.moveFromTo(this.playerPos, nextPosition)
                this.playerPos = nextPosition
                // this.game.player.moveOneStep()
                // this.processNpcs(this.game.gameMap.tileAt(nextPosition))
                // } else {
                //     break update_based_on_event
                // }
                break
            case 'Escape':
            case 'Q':
            case 'q':
            case 'P':
            case 'p':
                console.log(`Received event: ${event.key}`)
                // this.gameData.setState(new PausedState(this.gameData))
                break
            default:
                console.log('Default')
                break
        }
        // this.screen.updateHealth(this.game.player.health)
        this.update()
        // }
        // if (this.gemFound()) {
        //     this.game.gameWon()
        // }
        // if (this.game.gameIsOver()) {
        //     this.game.gameOver()
        // }
    }

    popChanges() {
        const changes = this.changeList
        this.changeList = []
        return changes
    }

    tileAt(pos) {
        return this.tiles[pos.y][pos.x]
    }

    classesAtPosition(x, y, peek) {
        const tile = this.tileAt({x, y})
        if (tile.isVisited)
            return [tile.terrain]

        if (peek)
            return [tile.terrain, 'peek']

        return [tile.terrain, 'cloaked']
    }

    update() {
        const cells = this.container.children
        const changeList = this.popChanges()
        const idx = (c) => (c.y * this.mapWidth) + c.x
        changeList.forEach((change) => {
            cells[idx(change)].classList = 'cell'
            cells[idx(change)].classList.add(...this.classesAtPosition(change.x, change.y, change.peek))
            if (this.playerPos.x === change.x && this.playerPos.y === change.y) {
                cells[(change.y * this.mapHeight) + change.x].classList = ['player']
            }
        })
    }
}

// let container = document.getElementById('chessboard')
let container = document.querySelector('#chessboard')
let gameMap = new GameMap(container, {width: 15, height: 15})

gameMap.build()

let handleEvent = gameMap.handleEvent.bind(gameMap)
window.addEventListener('keydown', handleEvent)

